package com.xwj.coupon.user.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Singleton;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;

import com.xwj.coupon.user.common.constant.MerchantAdminRedisConstant;
import com.xwj.coupon.user.common.constant.UserCouponConstant;
import com.xwj.coupon.user.common.context.UserContext;
import com.xwj.coupon.user.common.enums.UserCouponStatusEnum;
import com.xwj.coupon.user.dto.req.*;
import com.xwj.coupon.user.dto.resp.CouponTemplateQueryRespDTO;
import com.xwj.coupon.user.framework.exception.ClientException;
import com.xwj.coupon.user.framework.exception.ServiceException;
import com.xwj.coupon.user.framework.result.Result;
import com.xwj.coupon.user.mq.message.CreateUserCouponMessageBody;
import com.xwj.coupon.user.mq.producer.AssignUserCouponMqProducer;
import com.xwj.coupon.user.repository.mysql.entity.CouponSettlementDO;
import com.xwj.coupon.user.repository.mysql.entity.CouponTemplateDO;
import com.xwj.coupon.user.repository.mysql.entity.UserCouponDO;
import com.xwj.coupon.user.repository.mysql.mapper.CouponSettlementMapper;
import com.xwj.coupon.user.repository.mysql.mapper.CouponTemplateMapper;
import com.xwj.coupon.user.repository.mysql.mapper.UserCouponMapper;
import com.xwj.coupon.user.repository.redis.CouponTemplateDao;
import com.xwj.coupon.user.repository.redis.UserCouponRedisDao;
import com.xwj.coupon.user.service.CouponTemplateService;
import com.xwj.coupon.user.service.UserCouponService;
import com.xwj.coupon.user.service.rpc.CouponAdminRpcService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.producer.SendResult;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;

import java.math.BigDecimal;
import java.util.*;


/**
 * 用户优惠券业务逻辑实现层
 * <p>
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class UserCouponServiceImpl implements UserCouponService {
    private final CouponAdminRpcService couponAdminRpcService;
    private final CouponTemplateDao couponRedisDao;
    //private final UserCouponRedisDao userCouponRedisDao;
    private final UserCouponMapper userCouponMapper;
    private final StringRedisTemplate stringRedisTemplate;
    private final TransactionTemplate transactionTemplate;
    private final CouponTemplateMapper couponTemplateMapper;
    private final AssignUserCouponMqProducer assignUserCouponMqProducer;


    private final static String STOCK_DECREMENT_AND_SAVE_USER_RECEIVE_LUA_PATH = "lua/stock_decrement_and_save_user_receive.lua";

    private void preValidate(CouponTemplateDO templateDO){
        String userCouponZsetKey = String.format(UserCouponConstant.USER_COUPON_REDIS_KEY,UserContext.getUserId());
        Long templateId= templateDO.getId();
        int stock = templateDO.getStock();
        if(stock == 0){
            log.info("优惠券库存不足，领取失败");
            throw new ServiceException("优惠券库存不足，领取失败");
        }
        Long userTotalCount = stringRedisTemplate.opsForZSet().size(userCouponZsetKey);
        if(userTotalCount >= UserCouponConstant.USER_MAX_COUPON_COUNT){
            log.info("用户总领取数量超量，领取失败");
            throw new ServiceException("用户总领取数量超量，领取失败");
        }
    }

    private boolean decreaseCouponStock(Long couponTemplateId){
//        Result<Void> result = couponAdminRpcService.decreaseNumberCouponTemplate(new CouponTemplateNumberReqDTO(couponTemplateId, 1));
//        log.info("RPC调用扣库存，result:{}",result);
//        if(result.isFail()){
//            throw new ServiceException("RPC调用扣库存异常");
//        }
//        return true;
        return couponTemplateMapper.decreaseNumberCouponTemplate(couponTemplateId,1) == 1;
    }
    private boolean increaseCouponStock(Long couponTemplateId){
//        Result<Void> result = couponAdminRpcService.increaseNumberCouponTemplate(new CouponTemplateNumberReqDTO(couponTemplateId, 1));
//        log.info("RPC调用加库存，result:{}",result);
//        if(result.isFail()){
//            throw new ServiceException("RPC调用加库存异常");
//        }
//        return true;
        couponTemplateMapper.increaseNumberCouponTemplate(couponTemplateId,1);
        return true;
    }
    private Long createUserCoupon(CouponTemplateDO couponTemplate,Integer receiveCount, Long userId){
        Date now = new Date();
        Date validEndTime = couponTemplate.getValidEndTime();
        UserCouponDO userCouponDO = UserCouponDO.builder()
                .couponTemplateId(couponTemplate.getId())
                .userId(userId)
                .source(couponTemplate.getSource())
                .receiveCount(receiveCount)
                .status(UserCouponStatusEnum.UNUSED.getCode())
                .receiveTime(now)
                .validStartTime(now)
                .validEndTime(validEndTime)
                .build();
        userCouponMapper.insert(userCouponDO);
        return userCouponDO.getId();
    }

    //核心逻辑3,4,5
    //3.mysql扣减库存 4.mysql增加用户优惠券记录 //5.redis增加领取记录（zset存templateId_couponId）
    public void core(CouponTemplateDO template,Integer receiveCount,String userId){
        Long couponTemplateId = template.getId();
        String userCouponZsetKey = String.format(UserCouponConstant.USER_COUPON_REDIS_KEY,userId);
        String userCouponTemplateLimitCacheKey = String.format(UserCouponConstant.USER_COUPON_TEMPLATE_LIMIT_REDIS_KEY, userId, couponTemplateId);
        transactionTemplate.executeWithoutResult(status -> {
            boolean decreaseStockFlag = false;
            Long addUserCouponId = null;
            try {
                //3.mysql扣减库存
                decreaseStockFlag = decreaseCouponStock(couponTemplateId);

                //4.mysql增加用户优惠券记录
                addUserCouponId = createUserCoupon(template, receiveCount,Long.parseLong(UserContext.getUserId()));

                //5.redis增加领取记录（zset存templateId_couponId）
                stringRedisTemplate.opsForZSet().add(userCouponZsetKey, couponTemplateId+"_"+addUserCouponId, template.getValidEndTime().getTime());
                log.info("redis增加领取记录完成");
                //throw new Exception("模拟redis异常");

            } catch (Exception e) {
                log.error("用户领取优惠券失败", e); // 记录完整异常信息，便于排查
                status.setRollbackOnly();
                //回滚redis库存和领取数量
                couponRedisDao.incrementStock(couponTemplateId,1);
                stringRedisTemplate.opsForValue().decrement(userCouponTemplateLimitCacheKey);
                log.info("回滚redis库存和领取数量完成");

                if (decreaseStockFlag) {
                    // 情况2：库存已扣减，但用户优惠券未创建成功，需要回滚库存
                    log.warn("回滚优惠券库存，couponTemplateId:{}", couponTemplateId);
                    boolean rollbackSuccess = increaseCouponStock(couponTemplateId);
                    if (!rollbackSuccess) {
                        log.error("库存回滚失败，couponTemplateId:{}", couponTemplateId);
                        // 这里可以考虑发送告警通知人工处理
                    }
                }
                if (addUserCouponId != null){
                    stringRedisTemplate.opsForZSet().remove(userCouponZsetKey, couponTemplateId+"_"+addUserCouponId);
                    log.info("回滚redis领取记录完成");
                }
                if (e instanceof ServiceException) {
                    throw (ServiceException) e;
                }
                if (e instanceof DuplicateKeyException) {
                    log.error("用户重复领取优惠券，用户ID：{}，优惠券模板ID：{}", UserContext.getUserId(), couponTemplateId);
                    throw new ServiceException("用户重复领取优惠券");
                }
                // 包装并抛出异常，不暴露底层异常细节
                throw new ServiceException("用户领取优惠券失败，请稍后重试");
            }
        });
    }
    @Override
    public void redeemUserCoupon(CouponTemplateRedeemReqDTO requestParam) {
        Long couponTemplateId = requestParam.getCouponTemplateId();
        CouponTemplateDO template = couponRedisDao.getCouponTemplateById(couponTemplateId);
        log.info("领取优惠券，template:{}", template);
        preValidate(template);
        log.info("前置校验完成，开始领取优惠券");
        // 获取 LUA 脚本，并保存到 Hutool 的单例管理容器，下次直接获取不需要加载
        DefaultRedisScript<List> buildLuaScript = Singleton.get(STOCK_DECREMENT_AND_SAVE_USER_RECEIVE_LUA_PATH, () -> {
            DefaultRedisScript<List> redisScript = new DefaultRedisScript<>();
            redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource(STOCK_DECREMENT_AND_SAVE_USER_RECEIVE_LUA_PATH)));
            redisScript.setResultType(List.class);
            return redisScript;
        });

        //改：1.扣redis库存 2.redis增加领取数量 3.mysql扣减库存 4.mysql增加用户优惠券记录 //5.redis增加领取记录（zset存templateId_couponId）

        // 1.扣redis库存 2.redis增加领取数量
        Integer fetchLimit = template.getFetchLimit();

        // 执行 LUA 脚本进行扣减库存以及增加 Redis 用户领券记录次数
        String couponTemplateCacheKey = String.format(MerchantAdminRedisConstant.COUPON_TEMPLATE_KEY, couponTemplateId);
        String userCouponTemplateLimitCacheKey = String.format(UserCouponConstant.USER_COUPON_TEMPLATE_LIMIT_REDIS_KEY, UserContext.getUserId(), requestParam.getCouponTemplateId());
        List<Long> stockDecrementLuaResult = stringRedisTemplate.execute(
                buildLuaScript,
                ListUtil.of(couponTemplateCacheKey, userCouponTemplateLimitCacheKey),
                String.valueOf(template.getValidEndTime().getTime() / 1000), String.valueOf(fetchLimit)
        );
        log.info("执行LUA完成：1.扣redis库存 2.redis增加领取数量,result:{}", stockDecrementLuaResult);
        try {
            if(stockDecrementLuaResult.size()<2){
                log.info("执行LUA失败,result:{}", stockDecrementLuaResult);
                throw new ServiceException("执行LUA失败：1.扣redis库存 2.redis增加领取数量");
            }
            if(stockDecrementLuaResult.get(0) == 1){
                log.info("库存不足，领取失败");
                throw new ServiceException("库存不足，领取失败");
            }
            if(stockDecrementLuaResult.get(0) == 2){
                log.info("用户单张券领取数量超量，领取失败");
                throw new ServiceException("用户单张券领取数量超量，领取失败");
            }
        } catch (Exception e) {
            if(e instanceof ServiceException){
                throw (ServiceException) e;
            }
            //回滚redis库存和领取数量
            couponRedisDao.incrementStock(couponTemplateId,1);
            stringRedisTemplate.opsForValue().decrement(userCouponTemplateLimitCacheKey);
            log.error("回滚redis库存和领取数量完成, ", e);
            throw new ServiceException("领取优惠券失败");
        }
        core(template,stockDecrementLuaResult.get(1).intValue(),UserContext.getUserId());
        // 全部操作成功
        log.info("用户领取优惠券成功，couponTemplateId:{}", couponTemplateId);

    }

    @Override
    public void coreAssignUserCoupon(CouponTemplateDO template,Integer receiveCount,String userId){
        Long couponTemplateId = template.getId();
        String userCouponZsetKey = String.format(UserCouponConstant.USER_COUPON_REDIS_KEY,userId);
        String userCouponTemplateLimitCacheKey = String.format(UserCouponConstant.USER_COUPON_TEMPLATE_LIMIT_REDIS_KEY, userId, couponTemplateId);
        transactionTemplate.executeWithoutResult(status -> {
            boolean decreaseStockFlag = false;
            Long addUserCouponId = null;
            try {
                //3.mysql扣减库存
                decreaseStockFlag = decreaseCouponStock(couponTemplateId);

                //4.mysql增加用户优惠券记录
                addUserCouponId = createUserCoupon(template, receiveCount,Long.parseLong(userId));

                //5.redis增加领取记录（zset存templateId_couponId）
            } catch (Exception e) {
                log.error("用户领取优惠券失败", e); // 记录完整异常信息，便于排查
                status.setRollbackOnly();
                //回滚redis库存和领取数量 回滚可能造成问题
//                couponRedisDao.incrementStock(couponTemplateId,1);
//                stringRedisTemplate.opsForValue().decrement(userCouponTemplateLimitCacheKey);
                log.info("回滚redis库存和领取数量完成");
                if (e instanceof ServiceException) {
                    throw (ServiceException) e;
                }
                if (e instanceof DuplicateKeyException) {
                    log.warn("用户重复领取优惠券，用户ID：{}，优惠券模板ID：{}", userId, couponTemplateId);
                    throw new ServiceException("用户重复领取优惠券");
                }
                // 包装并抛出异常，不暴露底层异常细节
                throw new ServiceException("用户领取优惠券失败，请稍后重试");
            }
        });
    }


    @Override
    public void redeemUserCouponByMQ(CouponTemplateRedeemReqDTO requestParam) {
        Long couponTemplateId = requestParam.getCouponTemplateId();
        CouponTemplateDO template = couponRedisDao.getCouponTemplateById(couponTemplateId);
        log.info("领取优惠券，template:{}", template);
        preValidate(template);
        log.info("前置校验完成，开始领取优惠券");
        // 获取 LUA 脚本，并保存到 Hutool 的单例管理容器，下次直接获取不需要加载
        DefaultRedisScript<List> buildLuaScript = Singleton.get(STOCK_DECREMENT_AND_SAVE_USER_RECEIVE_LUA_PATH, () -> {
            DefaultRedisScript<List> redisScript = new DefaultRedisScript<>();
            redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource(STOCK_DECREMENT_AND_SAVE_USER_RECEIVE_LUA_PATH)));
            redisScript.setResultType(List.class);
            return redisScript;
        });

        //改：1.扣redis库存 2.redis增加领取数量 3.mysql扣减库存 4.mysql增加用户优惠券记录 //5.redis增加领取记录（zset存templateId_couponId）

        // 1.扣redis库存 2.redis增加领取数量
        Integer fetchLimit = template.getFetchLimit();

        // 执行 LUA 脚本进行扣减库存以及增加 Redis 用户领券记录次数
        String couponTemplateCacheKey = String.format(MerchantAdminRedisConstant.COUPON_TEMPLATE_KEY, couponTemplateId);
        String userCouponTemplateLimitCacheKey = String.format(UserCouponConstant.USER_COUPON_TEMPLATE_LIMIT_REDIS_KEY, UserContext.getUserId(), requestParam.getCouponTemplateId());
        List<Long> stockDecrementLuaResult = stringRedisTemplate.execute(
                buildLuaScript,
                ListUtil.of(couponTemplateCacheKey, userCouponTemplateLimitCacheKey),
                String.valueOf(template.getValidEndTime().getTime() / 1000), String.valueOf(fetchLimit)
        );
        log.info("执行LUA完成：1.扣redis库存 2.redis增加领取数量,result:{}", stockDecrementLuaResult);

        if(stockDecrementLuaResult.size()<2){
            log.info("执行LUA失败,result:{}", stockDecrementLuaResult);
            throw new ServiceException("执行LUA失败：1.扣redis库存 2.redis增加领取数量");
        }
        if(stockDecrementLuaResult.get(0) == 1){
            log.info("库存不足，领取失败");
            throw new ServiceException("库存不足，领取失败");
        }
        if(stockDecrementLuaResult.get(0) == 2){
            log.info("用户单张券领取数量超量，领取失败");
            throw new ServiceException("用户单张券领取数量超量，领取失败");
        }
        log.info("开始发消息队列");
        String uniqueKey = UUID.randomUUID().toString();
        CreateUserCouponMessageBody messageBody = new CreateUserCouponMessageBody(template, stockDecrementLuaResult.get(1).intValue(), UserContext.getUserId(),uniqueKey);
        assignUserCouponMqProducer.sendMessage(messageBody, template.getId().toString());
        // 全部操作成功
        log.info("用户领取优惠券成功，couponTemplateId:{}", couponTemplateId);
    }



    @Override
    public List<Long> findCouponIdList(Long shopNumber) {
        return List.of();
    }

    @Override
    public List<CouponTemplateQueryRespDTO> findUserCouponTemplateList(Long shopNumber) {
        String userId = UserContext.getUserId();
        String userCouponZsetKey = String.format(UserCouponConstant.USER_COUPON_REDIS_KEY,UserContext.getUserId());
        Set<String> set = stringRedisTemplate.opsForZSet().range(userCouponZsetKey, 0, -1);
        log.info("用户优惠券RedisSet,userId:{},set:{}", userId, set);
        if(CollectionUtil.isEmpty(set)){
            return List.of();
        }
        else{
            List<Long> couponTemplateIds = new ArrayList<>();
            List<Long> userCouponIds = new ArrayList<>();
            set.forEach(str -> {
                String[] s = str.split("_");
                couponTemplateIds.add(Long.parseLong(s[0]));
                userCouponIds.add(Long.parseLong(s[1]));
            });
            List<CouponTemplateDO> couponTemplateDOS = couponRedisDao.batchGetCouponTemplateWithPipeline(couponTemplateIds);
            List<CouponTemplateQueryRespDTO> result = new ArrayList<>();
            for (int i = 0; i < couponTemplateDOS.size(); i++) {
                CouponTemplateDO couponTemplate = couponTemplateDOS.get(i);
                if(couponTemplate!=null && (ObjectUtil.isNull(shopNumber) || shopNumber.equals(couponTemplate.getShopNumber()))){
                    CouponTemplateQueryRespDTO respDTO = BeanUtil.toBean(couponTemplateDOS.get(i), CouponTemplateQueryRespDTO.class);
                    respDTO.setUserCouponId(userCouponIds.get(i));
                    result.add(respDTO);
                }
            }
            return result;
        }
    }
}
